package netnamespace

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"runtime"
	"time"

	"github.com/vishvananda/netns"
)

func init() {
	log.SetPrefix("[netns]")
}

// 网络命名空间的相关操作
func Run() {
	// ！！！看https://pkg.go.dev/github.com/vishvananda/netns@v0.0.5#pkg-overview
	// ！！！当我们使用 Set()的时候必须增加 LockOSThread() 将当前程序限制在启动的OS线程中，不要被runtime调度走了
	// ！！！因为 Set()这种切换命名空间的动作只会影响当前线程。
	// （虽然命名空间是一种进程级别的资源，会影响所有的线程，但是命名空间属于一种比较特殊的进程级别上下文，在线程调度过程中，线程只会根据自己记忆中的命名空间进行运行）
	runtime.LockOSThread()

	// 获取当前的网络命名空间
	curNetNs, err := netns.Get()
	if err != nil {
		log.Fatalf("netns.get error: %v", err)
	}
	defer curNetNs.Close()
	log.Printf("当前网络命名空间 fd:%d\n", curNetNs)

	// 创建一个新的netns, 并进入该空间
	newNetNs, err := netns.New()
	if err != nil {
		log.Fatalf("netns.new error: %v\n", err)
	}
	defer newNetNs.Close()

	log.Printf("新的网络命名空间 fd:%d\n", newNetNs)

	// 将当前进程切换到新的netns中【！！！其实没有必要，因为 New的时候已经switch了】
	err = netns.Set(newNetNs)
	if err != nil {
		log.Fatalf("切换到fd:%d的新网络空间出错: %v", newNetNs, err)
	}
	log.Printf("切换到fd:%d的新网络空间......\n", newNetNs)

	// 在新命名空间中执行一些常见操作，比如打印所有的网卡 ip link
	cmd := exec.Command("ip", "link")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		log.Fatalf("列出所有的网卡失败,err:%v\n", err)
	}

	fmt.Println()
	fmt.Println()

	// 返回原始的网络命名空间
	err = netns.Set(curNetNs)
	if err != nil {
		log.Fatalf("返回netns出错:%v\n", err)
	}
	log.Printf("返回之前的nets...\n")
	// 在之前的netns中再次执行 ip link
	cmd = exec.Command("ip", "link")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	_ = cmd.Run()

	// 创建一个有名字的网络命名空间
	netNsName := fmt.Sprintf("ns-%d", time.Now().Unix())
	newNamedNetNs, err := netns.NewNamed(netNsName)
	if err != nil {
		log.Fatalf("netns.NewNamed error:%v\n", err)
	}
	defer newNamedNetNs.Close()
	log.Printf("有名字的新网络命名空间被创建，名字是：%s, fd:%d\n", netNsName, newNamedNetNs)
	log.Printf("查看一下现在有多少个netns;有名字的netns不会随着进程的退出而被删除")
	cmd = exec.Command("ip", "netns", "show")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Run()
}
